package com.cino.strategy.impl;

import com.cino.annotation.ConditionalOnListProperty;
import com.cino.config.properties.LogStorageProperties;
import com.cino.entity.SysAuditLog;
import com.cino.strategy.AuditLogStorageStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 本地文件审计日志存储策略实现
 * 
 * 使用@ConditionalOnListProperty注解，仅当配置中支持local时才加载
 * 配置路径：ys.log.storage.supports，期望值：local
 * 
 * 功能：
 * - 将审计日志保存为.log文件到本地目录
 * - 默认保存路径：/data/admin/log/
 * - 支持从配置文件读取自定义路径
 */
@Slf4j
@Component
@ConditionalOnListProperty(name = "ys.log.storage.supports", havingValue = "local")
public class LocalFileAuditLogStorageStrategy implements AuditLogStorageStrategy {
    
    @Autowired
    private LogStorageProperties logStorageProperties;
    
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private static final SimpleDateFormat FILE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
    
    @PostConstruct
    public void init() {
        if (logStorageProperties != null && logStorageProperties.getLocal() != null &&
                StringUtils.hasText(logStorageProperties.getLocal().getStoragePath())) {
            log.info(">>> [本地文件策略] 使用配置文件路径: {}", logStorageProperties.getLocal().getStoragePath());
        } else {
            log.error(">>> [本地文件策略] 错误：未配置 storage-path，请在 application.yml 中配置 ys.file.storage.local.storage-path");
            //TODO替换BusinessException
            throw new IllegalStateException("本地文件存储策略需要配置 storage-path");
        }
        
        log.info(">>> [本地文件策略] 初始化完成");
    }
    
    @Override
    @Async("auditLogExecutor")
    public void saveAuditLog(SysAuditLog sysAuditLog) {
        try {
            // 获取保存路径
            String logPath = getLogPath();
            
            // 生成日志内容
            String logContent = formatLogContent(sysAuditLog);
            
            // 生成文件名（按日期分组）
            String fileName = generateFileName(sysAuditLog.getObjectType());
            
            // 保存到文件
            try {
                saveToFile(logPath, fileName, logContent);
            } catch (IOException e) {
                //TODO替换BusinessException
                throw new RuntimeException("保存审计日志到文件失败", e);
            }
            
            log.info(">>> [本地文件策略][线程:{}] 审计日志保存成功: {}/{}", 
                Thread.currentThread().getName(), logPath, fileName);
                
        } catch (Exception e) {
            log.error(">>> [本地文件策略][线程:{}] 审计日志保存失败: {}", 
                Thread.currentThread().getName(), e.getMessage(), e);
            throw e;
        }
    }
    
    @Override
    @Async("auditLogExecutor")
    public void saveAuditLog(String objectId, String objectType, String content, String operationType, String storageTable,
                             Integer auditStatus, String errorMessage, Long duration, String title) {
        try {
            // 创建审计日志实体
            SysAuditLog sysAuditLog = new SysAuditLog();
            sysAuditLog.setObjectId(objectId);
            sysAuditLog.setObjectType(objectType);
            sysAuditLog.setAuditContent(content);
            sysAuditLog.setOperationType(operationType);
            sysAuditLog.setCreateTime(new Date());
            
            // 设置新增字段
            sysAuditLog.setAuditStatus(auditStatus);
            sysAuditLog.setErrorMessage(errorMessage);
            sysAuditLog.setDuration(duration);
            sysAuditLog.setTitle(title);
            
            // 注意：storageTable在本地文件存储中不适用，但保留参数兼容性
            log.debug(">>> [本地文件策略] storageTable参数({})在本地文件存储中被忽略", storageTable);
            
            // 调用实体保存方法
            saveAuditLog(sysAuditLog);
            
        } catch (Exception e) {
            log.error(">>> [本地文件策略] 审计日志保存失败: {}", e.getMessage(), e);
            throw e;
        }
    }
    
    @Override
    public String getStrategyType() {
        return "local";
    }
    
    @Override
    public boolean isAvailable() {
        try {
            String logPath = getLogPath();
            File logDir = new File(logPath);
            
            // 检查目录是否存在，不存在则尝试创建
            if (!logDir.exists()) {
                boolean created = logDir.mkdirs();
                if (!created) {
                    log.warn(">>> [本地文件策略] 无法创建日志目录: {}", logPath);
                    return false;
                }
            }
            
            // 检查目录是否可写
            if (!logDir.canWrite()) {
                log.warn(">>> [本地文件策略] 日志目录不可写: {}", logPath);
                return false;
            }
            
            return true;
        } catch (Exception e) {
            log.warn(">>> [本地文件策略] 可用性检查失败: {}", e.getMessage());
            return false;
        }
    }
    
    /**
     * 获取日志保存路径
     * 从配置文件读取，必须配置否则抛出异常
     */
    private String getLogPath() {
        // 从配置文件读取路径（初始化时已确保配置存在）
        if (logStorageProperties != null && 
            logStorageProperties.getLocal() != null && 
            logStorageProperties.getLocal().getStoragePath() != null) {
            String logPath = logStorageProperties.getLocal().getStoragePath().trim();
            log.debug(">>> [本地文件策略] 使用日志保存路径: {}", logPath);
            return logPath;
        }
        
        // 如果到这里说明配置有问题，抛出异常
        //TODO替换BusinessException
        throw new IllegalStateException("本地文件存储路径未配置");
    }
    
    /**
     * 格式化日志内容
     */
    private String formatLogContent(SysAuditLog sysAuditLog) {
        StringBuilder sb = new StringBuilder();
        
        // 时间戳
        String timestamp = DATE_FORMAT.format(sysAuditLog.getCreateTime() != null ?
                                             sysAuditLog.getCreateTime() : new Date());
        sb.append("[").append(timestamp).append("] ");
        
        // 操作类型
        sb.append("[").append(sysAuditLog.getOperationType()).append("] ");
        
        // 对象类型和ID
        sb.append("[").append(sysAuditLog.getObjectType()).append(":")
          .append(sysAuditLog.getObjectId()).append("] ");
        
        // 审计状态
        if (sysAuditLog.getAuditStatus() != null) {
            sb.append("[状态:").append(sysAuditLog.getAuditStatus() == 1 ? "成功" : "失败").append("] ");
        }
        
        // 耗时
        if (sysAuditLog.getDuration() != null) {
            sb.append("[耗时:").append(sysAuditLog.getDuration()).append("ms] ");
        }
        
        // 标题
        if (sysAuditLog.getTitle() != null && !sysAuditLog.getTitle().trim().isEmpty()) {
            sb.append("[标题:").append(sysAuditLog.getTitle()).append("] ");
        }
        
        // 错误信息
        if (sysAuditLog.getErrorMessage() != null && !sysAuditLog.getErrorMessage().trim().isEmpty()) {
            sb.append("[错误:").append(sysAuditLog.getErrorMessage()).append("] ");
        }
        
        // 日志内容
        sb.append(sysAuditLog.getAuditContent());
        
        return sb.toString();
    }
    
    /**
     * 生成文件名
     * 格式：audit_log_{objectType}_{yyyy-MM-dd}.log
     */
    private String generateFileName(String objectType) {
        String dateStr = FILE_DATE_FORMAT.format(new Date());
        String safeObjectType = (objectType != null && !objectType.trim().isEmpty()) ? 
                               objectType.replaceAll("[^a-zA-Z0-9_]", "_") : "unknown";
        return String.format("audit_log_%s_%s.log", safeObjectType, dateStr);
    }
    
    /**
     * 保存内容到文件
     */
    private void saveToFile(String logPath, String fileName, String content) throws IOException {
        // 确保目录存在
        File logDir = new File(logPath);
        if (!logDir.exists()) {
            boolean created = logDir.mkdirs();
            if (!created) {
                //TODO替换BusinessException
                throw new IOException("无法创建日志目录: " + logPath);
            }
        }
        
        // 写入文件（追加模式）
        File logFile = new File(logDir, fileName);
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(logFile, true))) {
            writer.write(content);
            writer.newLine();
            writer.flush();
        }
        
        log.debug(">>> [本地文件策略] 日志已写入文件: {}", logFile.getAbsolutePath());
    }
}
